package ua.com.hackaton.core;


import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.Log;
import android.view.View;
import ua.com.hackaton.Splash;
import ua.com.hackaton.core.awt.AffineTransform;
import ua.com.hackaton.core.awt.Dimension;
import ua.com.hackaton.core.awt.Font;
import ua.com.hackaton.core.awt.Point2D;
import ua.com.hackaton.core.awt.Rectangle;
import ua.com.hackaton.core.awt.Rectangle2D;
import ua.com.hackaton.core.awt.Shape;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;

/**
 * Created by IntelliJ IDEA.
 * User: Eugene Borshch
 */
public class CloudCreator
{

    public int rotateCOunt = 6;
    private int biggestSize = 54;
    private int smallestSize = 20;
    private List<Word> words = new ArrayList<Word>();
    private Random rand = new Random();
    private Rectangle2D imageSize = null;
    private double dRadius = 10.0;
    private int dDeg = 10;
    private int doSortType = 1;
    private Integer outputWidth = null;
    private boolean allowRotate = true;
    private int width;
    private int height;
    private Map<String, Integer> wordsMap;
    private Settings settings;


    public CloudCreator(int width, int height, Map<String, Integer> wordsMap, Settings settings)
    {
        this.width = width;
        this.height = height;
        this.wordsMap = wordsMap;
        this.settings = settings;
    }


    public void doLayout(Canvas canvas, View view)
    {
        this.imageSize = new Rectangle2D.Double(0, 0, 0, 0);
        if (this.words.isEmpty()) return;
        /** sort from biggest to lowest */

        switch (doSortType)
        {
            case 1:
            {
                Collections.sort(this.words, new Comparator<Word>()
                {
                    @Override
                    public int compare(Word w1, Word w2)
                    {
                        return (int) w2.getWeight() - (int) w1.getWeight();
                    }
                });
                break;
            }
            case 2:
            {
                Collections.sort(this.words, new Comparator<Word>()
                {
                    @Override
                    public int compare(Word w1, Word w2)
                    {
                        return (int) w1.getWeight() - (int) w2.getWeight();
                    }
                });
                break;
            }
            case 3:
            {
                Collections.sort(this.words, new Comparator<Word>()
                {
                    @Override
                    public int compare(Word w1, Word w2)
                    {
                        return w1.getText().compareToIgnoreCase(w2.getText());
                    }
                });
                break;
            }
            default:
            {
                Collections.shuffle(this.words, this.rand);
                break;
            }
        }
        Word first = this.words.get(0);
        double high = -Double.MAX_VALUE;
        double low = Double.MAX_VALUE;
        for (Word w : this.words)
        {
            high = Math.max(high, w.getWeight());
            low = Math.min(low, w.getWeight());
        }

        for (int i = 0; i < words.size(); i++)
        {
            Word w = words.get(i);
            String ff = w.getFontFamily();
            Typeface typeface = Typeface.createFromAsset(view.getContext().getAssets(), ff);

            int fontSize = (int) (((w.getWeight() - low) / (high - low)) * (this.biggestSize - this.smallestSize)) + this.smallestSize;
            Font font = new Font(fontSize, typeface);
            w.setFont(font);
            Log.w(Splash.TAG, "fontsize:" + fontSize);

            //TODO try to get text shape
            /*    TextLayout textLayout = new TextLayout(w.getText(), font, frc);
         Shape shape = textLayout.getOutline(null);*/

            Shape shape = getTextBounds(w.getText(), w.getFill(), font);
            // if (this.allowRotate && this.rand.nextBoolean()) {
            if (this.allowRotate && i % rotateCOunt == 0)
            {
                AffineTransform rotate = AffineTransform.getRotateInstance(
                        Math.PI / 2.0
                );
                shape = rotate.createTransformedShape(shape);
                w.setRotate(true);
            }
            Rectangle2D bounds = shape.getBounds2D();
            AffineTransform centerTr = AffineTransform.getTranslateInstance(-bounds.getCenterX(), -bounds.getCenterY());
            w.setShape(centerTr.createTransformedShape(shape));
            w.setBounds(w.getShape().getBounds2D());
        }
        //  g.dispose();

        //first point
        //   Point2D.Double center = new Point2D.Double(width / 2, height / 2);

        Point2D.Double center = new Point2D.Double(0, 0);

        for (int i = 1; i < this.words.size(); ++i)
        {
            Word current = this.words.get(i);

            //calculate current center
            // center.x = width / 2;
            // center.y = height / 2;

            center.x = 0;
            center.y = 0;
            double totalWeight = 0.0;
            for (int prev = 0; prev < i; ++prev)
            {
                Word wPrev = this.words.get(prev);
                center.x += (wPrev.getBounds().getCenterX()) * wPrev.getWeight();
                center.y += (wPrev.getBounds().getCenterY()) * wPrev.getWeight();
                totalWeight += wPrev.getWeight();
            }
            center.x /= (totalWeight);
            center.y /= (totalWeight);

            boolean done = false;
            double radius = 0.5 * Math.min(
                    first.getBounds().getWidth(),
                    first.getBounds().getHeight());


            while (!done)
            {
                Log.w(Splash.TAG, "" + i + "/" + words.size() + " rad:" + radius);
                int startDeg = rand.nextInt(360);
                //loop over spiral
                int prev_x = -1;
                int prev_y = -1;
                for (int deg = startDeg; deg < startDeg + 360; deg += dDeg)
                {
                    double rad = ((double) deg / Math.PI) * 180.0;
                    int cx = (int) (center.x + radius * Math.cos(rad));
                    int cy = (int) (center.y + radius * Math.sin(rad));
                    if (prev_x == cx && prev_y == cy) continue;
                    prev_x = cx;
                    prev_y = cy;

                    AffineTransform moveTo = AffineTransform.getTranslateInstance(cx, cy);
                    Shape candidate = moveTo.createTransformedShape(current.getShape());

                    Rectangle2D bound1 = new Rectangle2D.Double(
                            current.getBounds().getX() + cx,
                            current.getBounds().getY() + cy,
                            current.getBounds().getWidth(),
                            current.getBounds().getHeight()
                    );

                    //any collision ?
                    int prev = 0;
                    for (prev = 0; prev < i; ++prev)
                    {

                        if (bound1.intersects(this.words.get(prev).getBounds()))
                        {
                            break;
                        }

                    }
                       //colision with outer world

                    //no collision: we're done
                    if (prev == i)
                    {
                        current.setShape(candidate);
                        current.setBounds(candidate.getBounds2D());
                        done = true;
                        break;
                    }
                }

                radius += this.dRadius;
            }
        }

        double minx = Integer.MAX_VALUE;
        double miny = Integer.MAX_VALUE;
        double maxx = -Integer.MAX_VALUE;
        double maxy = -Integer.MAX_VALUE;
        for (Word w : words)
        {
            minx = Math.min(minx, w.getBounds().getMinX() + 1);
            miny = Math.min(miny, w.getBounds().getMinY() + 1);
            maxx = Math.max(maxx, w.getBounds().getMaxX() + 1);
            maxy = Math.max(maxy, w.getBounds().getMaxY() + 1);
        }
        AffineTransform shiftTr = AffineTransform.getTranslateInstance(-minx, -miny);
        for (Word w : words)
        {
            w.setShape(shiftTr.createTransformedShape(w.getShape()));
            w.setBounds(w.getShape().getBounds2D());
        }
        this.imageSize = new Rectangle2D.Double(0, 0, maxx - minx, maxy - miny);
    }


    public static Rectangle2D getTextBounds(final String text, final Paint fm, Font font)
    {
        Rect bounds = new Rect();
        Paint p = new Paint(fm);

        p.setTextSize(font.getSize());
        p.setTypeface(font.getTypeFace());

        p.getTextBounds(text, 0, text.length(), bounds);

        return new Rectangle(bounds.width(), bounds.height());
    }

    public void random2()
    {
        Iterator<Map.Entry<String, Integer>> iterator = wordsMap.entrySet().iterator();
        int i = 0;
        while (iterator.hasNext())
        {
            Map.Entry<String, Integer> entry = iterator.next();

            String text = entry.getKey();

            Integer value = entry.getValue();
            if (value > 50)
                value = 50;
            Word w = new Word(text, value + 10);

            /** Paint initialization */
            Colors colors = new Colors();
            List<Paint> paints = settings.getColors(); //this should somehow sent from UI

            int index = rand.nextInt(paints.size());
            Paint c;
            if (index == 0)
                c = paints.get(index);
            else if (index == paints.size())
                c = paints.get(index - 1);
            else
                c = paints.get(index);
            w.setFill(new Paint(c));
            w.setStroke(new Paint(c));
            w.setLineHeight(1 + 2 * rand.nextFloat());


            List<String> fonts = settings.getFonts();

            index = rand.nextInt(fonts.size());
            String font;
            if (index == 0)
                font = fonts.get(index);
            else if (index == fonts.size())
                font = fonts.get(index - 1);
            else
                font = fonts.get(index);

            w.setFontFamily(font);

            words.add(w);
        }
    }

    public static void drawRotatedString(final Word text, final Canvas g2,
                                         final float x, final float y,
                                         Paint paint, double shiftX, double shiftY)
    {
        Matrix old = g2.getMatrix();
        Matrix matrix = new Matrix();
        // rotate the Bitmap
        float angle = (float) (Math.PI / 2.0);
        float angleDegree = (float) (angle * 180 / Math.PI);
        matrix.setRotate(angleDegree, x, y);


        g2.rotate(angleDegree, x, y);
        g2.drawText(text.getText(), x, y, paint);
                g2.rotate(-angleDegree, x, y);

    }

    public void drawCloud(Canvas g)
    {
        AffineTransform scale = new AffineTransform();
        Dimension dim = new Dimension(
                (int) this.imageSize.getWidth(),
                (int) this.imageSize.getHeight()
        );

        if (this.outputWidth != null)
        {
            double ratio = this.outputWidth / dim.getWidth();
            dim.width = this.outputWidth;
            dim.height = (int) (dim.getHeight() * ratio);
            scale = AffineTransform.getScaleInstance(ratio, ratio);
        }


        //TODO create correct transform
        /*Shape shape = new Rectangle2D();
        scale.createTransformedShape
        g.setViewport();
        g.tsetTransform(scale);*/
        for (Word w : this.words)
        {
            Paint c = w.getFill();

            Paint p = new Paint(c);
            p.setTextSize(w.getFont().getSize());

            p.setTypeface(w.getFont().getTypeFace());

            double imageX = imageSize.getWidth() / 2;
            double imageY = imageSize.getHeight() / 2;

            double displayX = width / 2;
            double displayY = height / 2;

            double shiftX = displayX - imageX;
            double shiftY = displayY - imageY;

            float y = (float) (w.getShape().getBounds().getY() + shiftY);
            float x = (float) (w.getShape().getBounds().getX() + shiftX);

            // g.drawCircle(x, y, 3, p);

            Log.w(Splash.TAG, w.getText() + "   " + "x = " + x + "  y = " + y);

            if (w.isRotate())
            {
                drawRotatedString(w, g, x, y, p, shiftX, shiftY);
            } else
            {
                Rectangle2D bounds2D = w.getShape().getBounds2D();
                g.drawText(w.getText(), x, (float) (y + bounds2D.getHeight()), p);
            }
        }
    }


    private List<Rectangle2D> getOuterBounds()
    {
        List<Rectangle2D> result = new ArrayList<Rectangle2D>();

        int bound = 1000;
        result.add(new Rectangle(0, -bound, width, 0));
        result.add(new Rectangle(0, height, width, height + bound));
        result.add(new Rectangle(-bound, 0, 0, height + bound));
        result.add(new Rectangle(width, -bound, width + bound, height + bound));
        return result;
    }
}
